const Tuho = function() { /* 해당 스크립트는 방문수집 및 클릭 수집을 위한 함수 제공, 기여매장정보 제공을 위한 함수 제공을 한다. 주로 스크립트 말미에 생성되는 TuhoClient의 sendLogToTopic, getPageId, getContributeInfo 등을 다른 스크립트에서 사용한다. 특히, 우리 앱이 SPA(Single Page App) 구조이다보니, GNB 영역은 하나의 페이지 개념이 아니라 컨텐츠 성격이다. 그렇다보니 페이지 수집에 있어 api에 의존성을 가질 수 밖에 없다. 이를 해결하기 위해 환님 스크립트 common.js에서 GNB 영역에 대한 load가 된 이후에 sendLogToTopic를 해주는 로직으로 구성되어 있다. 해당 내용은 dalgona/common/common.js 에서 ####DATA#### 를 검색하면 된다. 이 부분은 적극적으로 환님과 소통해야한다. 클릭 데이터 같은 경우도 환님 담당 스크립트인 start.js에 의존성을 가지고 있다. 여기서 생성된 이벤트함수로 클릭수집이 이뤄지고 있다. omniClick(클릭이벤트 반응 함수), buildReacting(수집로직구성), vnsBtnClick(비센즈), detailInnerBtnClick(기획전, 이벤트 동적 html 수집), insBtnClick(인사이더) 함수가 우리 수집에서 활용된다. 수집 추가할 대상이 있으면 함수를 추가하는 것 대신에 [data-customregister-wrap]을 클래스 속성으로 추가하는 방향으로 진행(환님이랑 협의된 사안) 가장 좋은 것은 data-cttn 을 추가하는 방향이다. */ /* PROPERTIES */ const logServerAddress = 'https://tuho.elandmall.co.kr'; // 수집서버 도메인 const itemDetailQueryString = '/i/item?'; // 상품페이지 path, 상품페이지인지 확인할 때 사용하기 위해 선언 const searchQueryString = '/s/srch?'; // 검색페이지 path, 검색페이지인지 확인할 때 사용하기 위해 선언 const swipeUrlString = '/h/swipehome?'; // 스와이프홈 path, 스와이프홈 페이지인지 확인할 때 사용하기 위해 선언 let swipehome_arr = [ 'P01401006', //이랜드몰_P_스와이프홈 10 'P07301006', //럭갤_P_스와이프홈 10 'P05301007', //슈펜_P_스와이프홈 10 'P11301004', //키디키디_P_스와이프홈 10 'P03501006', //로엠_P_스와이프홈 10 'P04201004', //후아유_P_스와이프홈 10 'P03901002', //폴더_P_스와이프홈 10 'P03601003', //클루_P_스와이프홈 10 'P03301003', //에블린_P_스와이프홈 10 'P12301003', //애니바디_P_스와이프홈 10 'P03701004', //스파오_P_스와이프홈 10 'P04301004', //미쏘_P_스와이프홈 10 'P01301004', //모던하우스_P_스와이프홈 10 'P03401003', //로이드_P_스와이프홈 10 'P04101003', //OST_P_스와이프홈 10 'P12401004', //클라비스_P_스와이프홈 10 'P12508002' // 업투맥스_p_스와이프홈 ]; var ip = ''; var url = ''; var referer = ''; var ua = typeof window.navigator === 'undefined' ? '' : window.navigator.userAgent; // userAgent var page_id = new Date().getTime(); // 스크립트가 선언될 때 바로 page_id가 생성된다. var page_no = ''; var android_app_page_no = ''; // 신경쓰지 말 것. var disp_mall_no = ''; var belong_disp_mall_no = ''; var media_dcode = ''; var search_filter_string = ''; // 검색페이지에서 생성되는 필터 조건 string var search_item_count = ''; // 검색페이지 결과 상품 수 var item_disp_ctg_id = ''; // 상품페이지 카테고리 코드 var item_sell_price = ''; // 상품페이지 판매가격 var item_discount_price = ''; // 상품페이지 할인가격 -- 사실상 같음 var search_fail_yn = ''; // 상품검색실패여부(검색결과가 0일 경우 실패처리한다.) var search_fail_keyword_yn = ''; // 전시팀에서 만든 키워드 실패여부(N이 성공, Y 가 실패), etc_analysis_info에 적재될 예정 var bf_yn = 'N'; // bf 캐시를 N처리 해놓고 사용했을 때 Y 처리한다. var check_contribute_cornerNo = ''; // 기여매장 확인 컬럼(절대 지우지 말것. 주문 서비스 에러남.) var previous_prams_info = {}; // 신경쓰지 말 것. /* 검색여부 함수, 사용안함 */ const getIsSearchPage = function(){ return isSearchPage; } /* 페이지정보 get 함수, 디버깅용으로 콘솔에서 사용 */ const getPageNo = function(){ return page_no; } /* 안드로이드앱에서 페이지정보 get 함수, 사용안함 */ const getPageNoForAndroidApp = function(){ return android_app_page_no; } /* 페이지정보를 set해주는 함수, 페이지정보가 빈값일 때 set해주는 로직을 위해 사용 */ const setPageNo = function(page_no){ page_no = page_no; } /* 사용안함 */ const setBackUpPageNo = function(page_no){ backup_page_no = page_no; } /* 브라우저에서 bf캐시사용 여부를 확인할 때 수집데이터 bf_yn를 수정해주는 함수 */ const setBfYn = function (yn) { bf_yn = yn } /* page_id를 얻는 함수, 절대 수정하지 말것. 다른 곳에서 사용 중. */ const getPageId = function () { return String(page_id); } /* page_id를 얻는 함수, 절대 수정하지 말것. 다른 곳에서 사용 중. */ const getPageIdInfo = function () { return 'pageId=' + page_id; } /* tuho 객체 외부에서 스와이프홈인지 판단하기 위해 사용하는 함수. */ const getSwipeHomeArr = function () { return swipehome_arr; } /* 사용안함, js에서 다룰만한 쿠키가 별로 없음.*/ const getCookieByName = function (name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? match[2] : undefined; } /* url에서 다양한 파라미터를 얻기위한 함수, 절대 수정 x */ const getQueryParams = function (keyValueString) { var params = {}; keyValueString.replace( /[?&]+([^=&]+)=([^&]*)/gi, function(str, key, value) { params[key] = value; } ); return params; } /* 미수집 데이터를 쿠키에 셋하기 위해 만들었으나 현재 모두 로컬스토리지로 저장하게 됨에 따라 사용하지 않음. */ const setCookie = function (cName, cValue) { cookies = cName + '=' + encodeURIComponent(cValue) + '; path=/ '; // 한글 깨짐을 막기위해 escape(cValue)를 합니다. cookies += ';domain=elandmall.co.kr; SameSite=None; Secure'; document.cookie = cookies; } /* 전송한 쿠키를 삭제하기 위해 만들었으나 현재 모두 로컬스토리지로 저장하게 됨에 따라 사용하지 않음. */ const deleteCookie = function (name) { // document.cookie=name+'=; path=/; ;domain=elandmall.co.kr; SameSite=None; Secure; expires='+(function(t){t.setDate(t.getDate()-1000);return t}); document.cookie=name+'=; path=/; ;domain=elandmall.co.kr; SameSite=None; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT' } /* 쿠키명을 입력하면 해당 value를 가져오는 함수. 기존부터 있던 함수이지만 사용하지 않음. */ const getCookie = function (cName) { cName = cName + '='; var cookieData = document.cookie; var start = cookieData.indexOf(cName); var cValue = ''; if(start != -1){ start += cName.length; var end = cookieData.indexOf(';', start); if(end == -1)end = cookieData.length; cValue = cookieData.substring(start, end); } return decodeURIComponent(cValue); } /* 검색어 실패여부를 확인하기 위해 사용되는 함수. 실패 사례가 다양해서 한번에 처리하는 함수. */ var isEmpty = function(value){ try{ if( value === '' || value === null || value === undefined || ( value !== null && typeof value === "object" && !Object.keys(value).length ) ){ return true }else if(typeof value ==='string' && value.trim() ===''){ return true }else { return false } }catch(e){ console.log('tuho isEmpty error : ', e) } }; const setLogDatas = function (topic, addOnDatas) { /* 가장 중요한 함수 중 하나로, 보낼 데이터를 set 해주는 함수이다. 보내기 전에 항상 작동하는 함수라고 생각하면 된다. 외부에서 sendLogToTopic 함수에서 호출되며 파라미터로 topic, addOndatas를 받는다. topic 은 방문이냐 클릭이냐에 따라 전달되며 addOnDatas는 환님 스크립트에서 주로 같이 넣어준다. */ if(window.UIPage) { if('tracking' === topic.toLowerCase()) { if(addOnDatas && addOnDatas.adhoc_yn){ /* adhoc_yn 일 경우에는 기타 수집이기 때문에 page_id를 변경하지 않는다. 이중으로 함수가 호출되기 때문에 */ page_id = page_id; page_no = page_no; }else { /* 정상일 경우 page_id는 기존에 처음 선언된 page_id를 사용하며 swipehome일 경우는 새로 생성해준다. 왜냐하면 swipehome의 경우는 tuho 객체가 새로 생성되지 않기 때문이다. 또한 swipehome일경우 pageNo를 그냥 사용하면 껍데기의 pageNo가 수집된다. 그러므로 추가적은 데이터 addOnDatas.pageNo를 통해 대체해주어야 한다. 이는 환님 스크립트에 반영되어 있으며 만약 그 정보가 있다면 기존 pageNo를 대체한다. 스와이프홈이 아니라면 전시기본 제공 정보인 window.UIPage.pageNo을 사용한다. reacting은 방문정보 tracking 이 set 된다음에 그 정보를 바탕으로 수집되는 경우이기 때문에 page_id, page_no를 별도로 업데이트 해주지 않는다. */ page_id = swipehome_arr.indexOf(window.UIPage.pageNo) > -1 ? new Date().getTime() : page_id; if (addOnDatas && addOnDatas.pageNo) { page_no = addOnDatas.pageNo; android_app_page_no = addOnDatas.pageNo; }else { page_no = window.UIPage.pageNo || ''; } } } /* window.UIPage 정보 수집 window.UIPage에는 데이터팀 요청 및 각 서비스 활용을 위해 여러 정보들이 있다. 각 매장의 추가정보가 필요하면 여기다가 추가해달라고 요청하면 된다. window.UIPage.dispMallNo : 전시몰 window.UIPage.belongDispMallNo : 기본몰 window.UIPage.searchResult : 검색관련 정보(filterString, count, fail_yn) window.UIPage.itemDetail : 상품페이지 관련 정보(dispCategoryNo, sellprice, dcPrice) */ disp_mall_no = window.UIPage.dispMallNo || ''; belong_disp_mall_no = window.UIPage.belongDispMallNo || ''; media_dcode = window.UIPage.mediaDcode || ''; if(window.UIPage.searchResult) { const searchResult = window.UIPage.searchResult; search_filter_string = searchResult.filterString; search_item_count = searchResult.count; search_fail_keyword_yn = searchResult.failKeywordYn } if(window.UIPage.itemDetail) { const itemDetail = window.UIPage.itemDetail; item_disp_ctg_id = itemDetail.dispCategoryNo; item_sell_price = itemDetail.sellprice; item_discount_price = itemDetail.dcPrice; } } // if try { if(topic==='reacting'){ /* reacting 에서 url을 수집할 때, gnb의 경우 dataset 함수가 너무 빨리 작동해서 url이 뒤바뀌는 케이스 발생. 그래서 기존 url을 널는 로직 추가.*/ if(addOnDatas && addOnDatas['data-cttn'] && addOnDatas['data-cttn'].substr(0,1) === 'g') { url = url === '' ? (window.location.href || '') : url; }else { /* gnb 영역외에는 기존 로직 */ url = typeof window.location === 'undefined' ? '' : (window.location.href || ''); } /* 이건 신경쓰지 말것 */ if(swipehome_arr.indexOf(page_no) > -1 && android_app_page_no != ''){ page_no = android_app_page_no; } }else { /* tracking, app url set */ url = typeof window.location === 'undefined' ? '' : (window.location.href || ''); } /* 검색어 페이지인지 여부 확인해서 검색어 페이지일경우 search_item_count 변수값 avail 체크한다음에 set 해주는 로직 */ var isSearchPage = url.indexOf(searchQueryString) > -1; if(isSearchPage && !isNaN(search_item_count) && !isEmpty(search_item_count)) { search_fail_yn = Number(search_item_count) > 0 ? 'N' : 'Y'; } /* referrer set */ referer = document.referrer || ''; } catch(error) { console.log(error); url = ''; referer = ''; } ; } // setLogDatas end const sendLogToTopic = function (topic, addOnDatas) { /* 데이터 전송함수. 다른 스크립트에서 해당 함수를 호출하여 데이터를 전송한다. 다른 곳에서 데이터 수집할 사례가 생긴다면, 해당 함수를 공유하면 된다. 이 함수에서는 addOnData를 최종 데이터셋에 추가하는 작업 및 데이터 전송하는 처리가 포함되어 있다. */ var reacting_ts=''; /* 보완로직에 의해 수집되는 reacting data의 실제 시간을 파악하기 위한 로직 */ if(topic === 'reacting'){ reacting_ts = new Date().getTime(); } if(!topic || typeof topic !== 'string') {return false;} /* 삭제하지 말 것. */ topic = topic.toLowerCase(); var isExist = false; /* topic 이 아래 세가지가 아니면 모든 작업 취소되는 로직 */ ['tracking', 'reacting', 'app'].forEach( function (tuhoTopic) { if(tuhoTopic === topic) {isExist = true;} }); if(!isExist) {return false;} /* 데이터셋 함수 호출 */ setLogDatas(topic, addOnDatas); /* 서버에 보내는 최종 데이터셋 */ const params = { ip: ip, url: url, referer: referer, ua: ua, page_id: page_id, page_no: page_no, disp_mall_no: disp_mall_no, belong_disp_mall_no: belong_disp_mall_no, media_dcode: media_dcode, search_filter_string: search_filter_string, search_item_count: search_item_count, item_disp_ctg_id: item_disp_ctg_id, item_sell_price: item_sell_price, item_discount_price: item_discount_price, search_fail_yn: search_fail_yn, bf_yn: bf_yn } /* 함수 호출시 파라미터에 addOnDatas가 있을 경우 최종 데이터셋에 추가하는 작업. etc, data-cttn 등의 데이터가 주로 이 작업을 통해 들어온다. */ if(addOnDatas && typeof addOnDatas === 'object') { Object.keys(addOnDatas).forEach(function(addOnKey) { params[addOnKey] = addOnDatas[addOnKey]; }); } /* search_fail_keyword_yn 이 있을때만 데이터 추가 */ if(search_fail_keyword_yn !== '' && typeof search_fail_keyword_yn !== undefined){ params['adhoc_search_fail_keyword_yn'] = search_fail_keyword_yn; } /* url, referrer에서 특수문자라던지, 기타 오류가 있어 전처리 해주는 함수 */ params.url = convertUrl(url); params.referer = convertReferer(referer); /* adhoc_yn이 있을경우 처리 과거 visenz 작업을 위해 추가했던 로직. visenz와 elandmall 수집을 동시에 진행하기 위해 별로 컬럼으로 분기처리 했음. 사실상 현재는 사용하고 있지 않으나 visenz처럼 한 페이지에서 이중으로 수집하는 카에스가 발생할 때 사용할 것. */ var adhoc_yn = 'N' if(addOnDatas && addOnDatas.adhoc_yn === 'Y') { adhoc_yn = 'Y' } /* 스와이프홈이 아니고 topic이 tracking일 경우 데이터를 보내기전 최종 데이터셋을 로컬스토리지에 넣고, 만약 해당 데이터 전송이 실패할 경우 해당 데이터를 다음 페이지에서 재전송해주기 위해 데이터를 로컬스토리지 넣는 로직 */ if(window && window.location.href.indexOf(swipeUrlString) > -1 && topic == 'tracking') { try{ setPreviousLog(params) }catch(error){ console.log(error) } } /* ★★★ send log to Tuho server - start (async with vanilla js) ★★★ 데이터 전송 로직, XMLHttpRequest(ajax)를 활용한다. httpRequest.open('POST', logServerAddress + '/log-management/' + topic, true) 에서 true는 비동기 설정 httpRequest.withCredentials = true 는 쿠키 등 인증정보를 송신할 수 있는 설정이다. */ var httpRequest = new XMLHttpRequest(); httpRequest.open('POST', logServerAddress + '/log-management/' + topic, true); httpRequest.withCredentials = true; /* 아래는 request가 정상적으로 처리되지 않았을 경우 보완로직. 성공할 경우는 기존에 로컬스토리지에 저장했던 정보에 setPreviousLog({sent_yn:'Y'}) 처리를 해주면서 별도 처리를 하지 않아도 되는 플래그를 저장한다. tracking은 매 페이지마다 전 페이지 정보를 갖고 있고 sent_yn을 확인하여 Y처리가 없을 경우 전송처리한다. reacting의 경우 실패시 handleRequestError() 처리를 통해서 로컬스토리지에 저장하고 전송한다. */ httpRequest.onreadystatechange = function () { if (httpRequest.readyState === XMLHttpRequest.DONE) { var result = httpRequest.response; if (httpRequest.status === 200 && topic == 'tracking'){ setPreviousLog({sent_yn:'Y'}); } else if (httpRequest.status === 0 && topic == 'reacting'){ setPreReactingLog(modPreviousLog(params, {adhoc_prev_flag:'Y', adhoc_reacting_ts: reacting_ts})); console.log('tuho click status 0!') } else { } } } function handleRequestError() { if (topic === 'reacting') { setPreReactingLog(modPreviousLog(params, {adhoc_prev_flag:'Y', adhoc_reacting_ts: reacting_ts})); console.log('tuho click abort! or onerror or timeout'); } }; httpRequest.onabort = handleRequestError; httpRequest.onerror = handleRequestError; httpRequest.ontimeout = handleRequestError; /* 요청 형식 및 헤더 설정 */ httpRequest.responseType = 'json'; httpRequest.setRequestHeader('Content-Type', 'application/json'); httpRequest.setRequestHeader('Cache-Control', 'no-cache'); /* adhoc_yn이 N일 경우는 보통 케이스, 그냥 전송, 보내고 난 뒤에는 bf_yn을 찍어준다. 왜냐하면 bf_yn이 Y로 되고 난뒤에 스와이프 홈에서는 그냥 계속 Y로 유지되는 것을 방지하고자. */ if(adhoc_yn !== 'Y') { if (swipehome_arr.indexOf(params.page_no) == -1) { httpRequest.send(JSON.stringify(params)); if(topic === 'tracking'){ bf_yn = 'N'; } } } else { httpRequest.send(JSON.stringify(params)); } } // sendLogToTopic end /* 스와이프홈 수집 보완을 위한 함수, 현재 사용하지 않음. */ const SetSwipeHomeReactingpreviousLog = function (addOnDatas) { const params = { ip: ip, url: url, referer: referer, ua: ua, page_id: page_id, page_no: page_no, disp_mall_no: disp_mall_no, belong_disp_mall_no: belong_disp_mall_no, media_dcode: media_dcode, search_filter_string: search_filter_string, search_item_count: search_item_count, item_disp_ctg_id: item_disp_ctg_id, item_sell_price: item_sell_price, item_discount_price: item_discount_price, search_fail_yn: search_fail_yn, bf_yn: bf_yn } if(addOnDatas && typeof addOnDatas === 'object') { Object.keys(addOnDatas).forEach(function(addOnKey) { params[addOnKey] = addOnDatas[addOnKey]; }); } params.url = convertUrl(url); params.referer = convertReferer(referer); // 이벤트 발생 당시에 바로 전송하지 않는다.(iOS 같은 경우 http 송신이 페이지이동간에 제한된다.) window.localStorage.setItem('SwipeHomePreReactingLog',JSON.stringify(params)); } // SetSwipeHomeReactingpreviousLog end /* url 전처리를 위한 함수 */ const convertUrl = function (url) { var result = ''; // url에서 스와이프홈시 url에 #때문에 URL 함수가 안먹는 케이스가 발생. 그래서 아래처럼 #을 제거해주는 작업 추가 var url = url.replace('/h/swipehome#?', '/h/swipehome?') var isSwipeHomePage = url.indexOf(swipeUrlString) > -1; if(isSwipeHomePage) { var swipeurl = new URL(url); result = swipeurl.origin + decodeURI(swipeurl.searchParams.get("region")) } else { result = url } return result } /* referrer 전처리 함수 */ const convertReferer = function (referer) { var result = ''; var referer = referer.replace('/h/swipehome#?', '/h/swipehome?') var isSwipeRef = referer.indexOf(swipeUrlString) > -1; if(isSwipeRef) { var swipeRefUrl = new URL(referer); var swipeRefParam = decodeURI(swipeRefUrl.searchParams.get("region")) if (referer.indexOf('&') > -1) { var params_query = getQueryParams(referer); delete params_query['region'] var queryString = '?' + new URLSearchParams(params_query); result = swipeRefUrl.origin + swipeRefParam + queryString; } else { result = swipeRefUrl.origin + swipeRefParam; } } else { result = referer; } return result } /* 전송 실패시 재전송하기 위한 함수, 쿠키에 넣는 방식인데 이제는 사용안함. */ const retryLogToTopic = function () { var previous_log = getCookie('tlog') if (previous_log) { var topic = previous_log.split('|||')[0]; var json_data = previous_log.split('|||')[1]; var httpRequest = new XMLHttpRequest(); httpRequest.open('POST', logServerAddress + '/log-management/' + topic, true); httpRequest.withCredentials = true; httpRequest.onreadystatechange = function () { if (httpRequest.readyState === XMLHttpRequest.DONE) { var result = httpRequest.response; if (httpRequest.status === 200) { // TODO REMOVE } else { } } } httpRequest.responseType = 'json'; httpRequest.setRequestHeader('Content-Type', 'application/json'); httpRequest.setRequestHeader('Cache-Control', 'no-cache'); // window.setTimeout 제거 httpRequest.send( json_data ); deleteCookie('tlog'); } } // retryLogToTopic end /* 리액팅 미수집 대상 재수집을 위한 함수, 에러시 로컬스토리지에 담고 재전송한다. */ const retryLogToReacting = function () { // 20231127 종종 미수집되는 페이지 수집을 위해 만들었음. var json_data = JSON.parse(window.localStorage.getItem('preReactingLog')); if(json_data) { var httpRequest = new XMLHttpRequest(); httpRequest.open('POST', logServerAddress + '/log-management/' + 'reacting', true); httpRequest.withCredentials = true; httpRequest.responseType = 'json'; httpRequest.setRequestHeader('Content-Type', 'application/json'); httpRequest.setRequestHeader('Cache-Control', 'no-cache'); // settimeout 제거 if (swipehome_arr.indexOf(json_data.page_no) == -1) { httpRequest.send(JSON.stringify(json_data)); } // deleteCookie('previousLog'); } window.localStorage.removeItem('preReactingLog'); } // retryLogToReacting end /* 스와이프홈 미수집 대상을 위한 함수, 현재는 사용안 함. */ const retrySwipeLogToReacting = function () { const currentTs = new Date().getTime(); var json_data = JSON.parse(window.localStorage.getItem('SwipeHomePreReactingLog')); if(json_data) { if(json_data.expire_date < currentTs) { window.localStorage.removeItem('SwipeHomePreReactingLog'); }else { var httpRequest = new XMLHttpRequest(); httpRequest.open('POST', logServerAddress + '/log-management/' + 'reacting', true); httpRequest.withCredentials = true; httpRequest.responseType = 'json'; httpRequest.setRequestHeader('Content-Type', 'application/json'); httpRequest.setRequestHeader('Cache-Control', 'no-cache'); httpRequest.send(JSON.stringify(json_data)); } } window.localStorage.removeItem('SwipeHomePreReactingLog'); } // retrySwipeLogToReacting end /* 스와이프홈 클릭이벤트 함수, 현재는 사용 안함. */ const SwipeHomeClickEventFunction = function(e, target){ // 스와이프홈 클릭 이벤트 수집 try { var target = e.target.closest(target); var params = {}; var swipeYN = swipehome_arr.indexOf(window.UIPage.pageNo); if(target && swipeYN != -1){ params['data-cttn'] = target.getAttribute('data-cttn'); var previous_corner_el = target.closest('[data-cnr-no]'); params['data-cnr-no'] = (typeof previous_corner_el !== undefined && previous_corner_el !==null) ? previous_corner_el.getAttribute('data-cnr-no') : null; params['expire_date'] = new Date().getTime() + 10 * 60 * 1000; // 10분 뒤가 만료시간 params['adhoc_yn'] = 'Y'; SetSwipeHomeReactingpreviousLog(params); } }catch(error){ console.log(error) } }; // SwipeHomeClickEventFunction end /* 트랙킹 재수집 함수, 미전송 대상 로컬스토리지에 저장 후 재전송. */ const retryLogToTracking = function () { // 20231127 종종 미수집되는 페이지 수집을 위해 만들었음. // var json_data = getCookie('previousLog'); var json_data = JSON.parse(window.localStorage.getItem('previousLog')); if(json_data && json_data.sent_yn == 'N') { var httpRequest = new XMLHttpRequest(); httpRequest.open('POST', logServerAddress + '/log-management/' + 'tracking', true); httpRequest.withCredentials = true; httpRequest.responseType = 'json'; httpRequest.setRequestHeader('Content-Type', 'application/json'); httpRequest.setRequestHeader('Cache-Control', 'no-cache'); if (swipehome_arr.indexOf(json_data.page_no) == -1) { httpRequest.send(JSON.stringify(json_data)); } } window.localStorage.removeItem('previousLog'); } // retryLogToTracking /* 코너정보가 있는지 확인하는 함수, 절대 건드리지 말것. */ const checkContributeInfo = function (e) { var target = $(e.currentTarget); var cornerEl = target.closest('[data-cnr-no]'); check_contribute_cornerNo = cornerEl.length ? $.trim(cornerEl.attr('data-cnr-no')) : ''; } /* page_id, corner_no를 가져올 수 있는 함수, 장바구니에서 사용. 절대 건드리지 말것. */ const getContributeInfo = function (e) { return { page_id: typeof getContributePageId(e) === 'undefined' ? '' : getContributePageId(e).replace(/#\s*$/, ''), // 공백이나 #이 들어가있으면 제거; corner_id: typeof getContributeCornerNo(e) === 'undefined' ? '' : getContributeCornerNo(e).replace(/#\s*$/, '') } } /* 기여매장 page_id를 가져오는 함수, url의 pageId 값을 가져온다. */ const getContributePageId = function (e) { var contribute_pageId = ''; var currentUrl = typeof window.location === 'undefined' ? '' : (window.location.href || ''); var isItemDetailPage = currentUrl.indexOf(itemDetailQueryString) > -1; if(isItemDetailPage) { var query = currentUrl.substring(currentUrl.indexOf(itemDetailQueryString) + itemDetailQueryString.length); var params = getQueryParams(query); contribute_pageId = params.pageId; } else { contribute_pageId = getPageId(); } return contribute_pageId; } /* 기여매장 page_id를 가져오는 함수, url의 cornerNo값을 가져온다. */ const getContributeCornerNo = function (e) { var contribute_cornerNo = ''; var currentUrl = typeof window.location === 'undefined' ? '' : (window.location.href || ''); /* 상품상세 페이지에서 특히 딜상품의 경우, preCornerNo가 정상적으로 작동 안하는 케이스가 있어 해당 처리를 해준 로직. */ var isItemDetailPage = currentUrl.indexOf(itemDetailQueryString) > -1; if(isItemDetailPage) { var query = currentUrl.substring(currentUrl.indexOf(itemDetailQueryString) + itemDetailQueryString.length); var params = getQueryParams(query); if(params.preCornerNo){ contribute_cornerNo = params.preCornerNo.indexOf('#') > -1 ? params.preCornerNo.substring(0,params.preCornerNo.indexOf('#')) : params.preCornerNo ; }; } else { // 이전에 저장해놓은 코너정보를 넣어줌 if (check_contribute_cornerNo != '') { contribute_cornerNo = check_contribute_cornerNo; } else { var target = $(e.currentTarget); var cornerEl = target.closest('[data-cnr-no]'); contribute_cornerNo = cornerEl.length ? $.trim(cornerEl.attr('data-cnr-no')) : ''; } } return contribute_cornerNo; } /* 트랙킹 재수집할 때 상품상세, 검색매장의 정보를 수집하기 위한 함수. 상품상세, 검색매장 load 시 이벤트함수가 호출되면 해당 함수가 호출되어 데이터 전송함. 아래 TuhoPageDetailInfo 이벤트 등록 함수가 있음. 이 이벤트가 상품상세, 검색매장 페이지에 등록되어있다. */ const setDetailInfo = function(){ if(previous_log){ if(window.UIPage.searchResult) { const searchResult = window.UIPage.searchResult; previous_log.search_filter_string = searchResult.filterString; previous_log.search_item_count = searchResult.count; var isSearchPage = url.indexOf(searchQueryString) > -1; var empty = isEmpty(previous_log.search_item_count) if(isSearchPage && !isNaN(previous_log.search_item_count) && !empty) { previous_log.search_fail_yn = Number(previous_log.search_item_count) > 0 ? 'N' : 'Y'; } /* failKeywordYn 수집을 위한 로직 추가 */ if(searchResult.failKeywordYn !== '' && typeof searchResult.failKeywordYn !== undefined){ previous_log['adhoc_search_fail_keyword_yn'] = searchResult.failKeywordYn } } if(window.UIPage.itemDetail) { const itemDetail = window.UIPage.itemDetail; previous_log.item_disp_ctg_id = itemDetail.dispCategoryNo; previous_log.item_sell_price = itemDetail.sellprice; previous_log.item_discount_price = itemDetail.dcPrice; } window.localStorage.setItem('previousLog',JSON.stringify(previous_log)); } } /* 로컬스토리지에 데이터를 셋하는 함수 */ const setPreviousLog = function(params) { Object.keys(params).forEach(function(key){ previous_log[key] = params[key]; }) window.localStorage.setItem('previousLog',JSON.stringify(previous_log)) } /* 전송할 데이터를 수정하는 함수 */ const modPreviousLog = function(params1, params2) { var result = params1; try { Object.keys(params2).forEach(function(key){ result[key] = params2[key]; }) }catch(error){ console.log('ModPreviousLog : ', error) } return result } /* 리액팅 데이터 로컬스토리지에 담는 함수 */ const setPreReactingLog = function(params) { try { window.localStorage.setItem('preReactingLog',JSON.stringify(params)) }catch (error){ console.log('setPreReactingLog error : ', error) } } /* 리액팅 데이터 로컬스토리지에 담는 함수 */ const setPreviousParamsInfo = function(params) { const currentDate = new Date().getTime(); if(params.expire_date && currentDate < params.expire_date) { Object.keys(params).forEach(function(key){ previous_prams_info[key] = params[key]; }) } window.localStorage.removeItem('previousPramsInfo') } /* 사용안함. */ const getPreviousParamsInfo = function() { return { page_id : previous_prams_info['pre_page_id'] === null ? '' : previous_prams_info['pre_page_id'] , corner_no : previous_prams_info['pre_corner_no'] === null ? '' : previous_prams_info['pre_corner_no'] } } /* 여기서부터는 tuho() 객체가 선언되면서 함수가 실행되는 부분. 객체가 생성 될 때 로컬스토리지에 있는 전 페이지 정보가 전송된다.(전송유무 판단하에) 그리고 생성 될 때 트랙킹백업 데이터를 로컬스토리지에 저장한다. */ retryLogToTracking(); retryLogToReacting(); // retrySwipeLogToReacting(); setLogDatas("tracking"); /* 백업데이터 셋 */ const previous_log = { ip: ip, url: convertUrl(url), referer: convertReferer(referer), ua: ua, page_id: page_id, page_no: page_no, disp_mall_no: disp_mall_no, belong_disp_mall_no: belong_disp_mall_no, media_dcode: media_dcode, search_filter_string: search_filter_string, search_item_count: search_item_count, item_disp_ctg_id: item_disp_ctg_id, item_sell_price: item_sell_price, item_discount_price: item_discount_price, search_fail_yn: search_fail_yn, bf_yn: bf_yn, sent_yn : 'N', // 실재로 보냈는지 판단 여부는 여기서 한다. adhoc_prev_flag : 'Y' // previous data 로 수집되었는지 여부 확인하기 } // if(url.indexOf(searchQueryString) !== -1 ){ // previous_log['adhoc_origin_search_item_count'] = search_item_count; // previous_log['adhoc_origin_search_faily_yn'] = search_fail_yn; // } /* 백업데이터(tracking) 로컬스토리지에 저장 */ window.localStorage.setItem('previousLog',JSON.stringify(previous_log)); /* 백업데이터(tracking) 상품상세, 검색매장 데이터 셋업을 위한 이벤트 생성 */ window.TuhoPageDetailInfo = new Event('TuhoPageDetailInfo'); /* 이벤트 등록 */ window.addEventListener('TuhoPageDetailInfo', function(){ setDetailInfo(); }); /* tuho 객체 최종 return 값, 외부에서 tuho객체 아래 return 값을 참조할 수 있다. */ return { bf_yn : bf_yn, setBfYn : setBfYn, getPageId : getPageId, getPageNoForAndroidApp : getPageNoForAndroidApp, getPageIdInfo : getPageIdInfo, getSwipeHomeArr : getSwipeHomeArr, sendLogToTopic : sendLogToTopic, retryLogToTopic : retryLogToTopic, checkContributeInfo : checkContributeInfo, getContributeInfo : getContributeInfo, getContributePageId : getContributePageId, getContributeCornerNo : getContributeCornerNo, getCookie : getCookie, retryLogToTracking : retryLogToTracking, setCookie : setCookie, setPageNo : setPageNo, setPreviousLog : setPreviousLog, getPageNo : getPageNo, setPreviousParamsInfo : setPreviousParamsInfo, getPreviousParamsInfo : getPreviousParamsInfo, SetSwipeHomeReactingpreviousLog : SetSwipeHomeReactingpreviousLog, SwipeHomeClickEventFunction : SwipeHomeClickEventFunction }; } /* Tuho.js 에서 tuho 객체(함수) 선언 후 바로 TuhoClient 전역 객체를 생성한다. 해당 객체로 다른 곳에서 활용한다. 그 아래 익명 함수를 통해 스크립트가 호출될 때 바로 실행한다. 익명함수에서는 tuho 객체 외부에서 실행되어야 할 함수를 정의하고 실행한다. */ var TuhoClient = Tuho(); (function (isUndefined) { /* 환님 스크립트에서 swipehome load 시 해당 함수가 호출되면 실제 pageNO가 셋되게 된다. */ window.TUHO_SWIPEHOME = { activate: function (prop) { var $target = prop.$target; var page_no = $target.find('input[name="swipe_pageno"]').val(); if(TuhoClient && page_no){ TuhoClient.setPageNo(page_no); } } }; /* 외부에서 실행될 함수 startup : pageshow 이벤트로 바인딩할 함수, 모든 페이지 소스가 load 된 뒤 tracking 수집을 한다.(swipehome 제외) startup_app : android app 에서 실행할 함수. android 상품상세에서 뒤로가기가 안먹어서 별로도 만든 함수. search_sort_filter_click_collect : 검색매장의 필터클릭을 수집하기 위한 함수. */ tuho_functions = { startup : function (e) { if (e.persisted) {TuhoClient = Tuho();} if (e.persisted || (window.performance.navigation.type === 2) || (performance.getEntriesByType("navigation")[0].type ==='back_forward')) { TuhoClient.setBfYn('Y'); } if(TuhoClient.getSwipeHomeArr().indexOf(window.UIPage.pageNo) == -1){TuhoClient.sendLogToTopic('tracking', {});}; }, startup_app : function (e) { var page_no_android = TuhoClient.getPageNoForAndroidApp(); TuhoClient = Tuho(); TuhoClient.setBfYn('Y'); if(TuhoClient.getSwipeHomeArr().indexOf(window.UIPage.pageNo) == -1){ TuhoClient.sendLogToTopic('tracking', {}); }else{ if(TuhoClient.getSwipeHomeArr().indexOf(page_no_android) == -1){ if(page_no_android !==''){ TuhoClient.setPageNo(page_no_android); TuhoClient.setPreviousLog({page_no : page_no_android}); } } }; }, search_sort_filter_click_collect : function(e){ try { var url = window.location.href; var isTargetPage = url.indexOf('/s/srch') > -1 || url.indexOf('/c/ctg') > -1 || url.indexOf('/b/brand') > -1; var selectElement = document.querySelector("[id*=veiw-sort]"); var liElement = document.querySelector("[id=sort]"); var TargetElement = selectElement ? selectElement : liElement; var EventTypeDict = { selectElement: "change", liElement: "click" } if(isTargetPage && TargetElement){ var eventType = (TargetElement === selectElement) ? EventTypeDict.selectElement : EventTypeDict.liElement; TargetElement.addEventListener(eventType, function(e){ try { if(e.target && (e.target.closest('[data-code]') || e.target.value)){ var selectedValue = (eventType === 'click') ? e.target.closest('[data-code]').getAttribute('data-code') : e.target.value; var params = {}; params['data-cttn'] = 'vs_'+selectedValue+'_0' //veiw-sort 로 하였음. var previous_corner_el = e.target.closest('[data-cnr-no]'); params['data-cnr-no'] = (typeof previous_corner_el !== undefined && previous_corner_el !==null) ? previous_corner_el.getAttribute('data-cnr-no') : null; TuhoClient.sendLogToTopic('reacting', params); }else{ console.log('컨텐츠를 누르지 않았습니다.') } }catch(e) { console.log('검색어 sort 클릭 이벤트 수집 에러 : ', e); } }); }; }catch(e){ console.log('검색어 sort 클릭 이벤트 수집 에러 : ', e); }; } }; /* 이벤드 등록 영역 startup, search_sort_filter_click_collect 등 이벤트를 등록한다. 또한 안드로이드 뒤로가기 수집을 위한 별로 windowTrigger.pageshow 이벤트를 등록한다.(이 이벤트는 절대 수정 금지!) */ window.addEventListener('pageshow', function(e) { tuho_functions.startup(e) }); document.addEventListener('DOMContentLoaded', function(e){ tuho_functions.search_sort_filter_click_collect(e); }); var eventName = 'windowTrigger.pageshow'; window.pageShowTriggerDispathEvent = eventName; window.addEventListener(eventName, function (e) { tuho_functions.startup_app(e); }); var event; // The custom event that will be created if(document.createEvent){ event = document.createEvent("HTMLEvents"); event.initEvent(eventName, true, true); event.eventName = eventName; } else { event = document.createEventObject(); event.eventName = eventName; event.eventType = eventName; } var PageShowTrigger = function () { if(document.createEvent){ window.dispatchEvent(event); } else { window.fireEvent("on" + event.eventType, event); } }; window.PageShowTrigger = PageShowTrigger; })();